package asteroids;

import java.io.*;
import java.net.URL;
import java.util.*;

import org.antlr.v4.runtime.RecognitionException;

import asteroids.model.programs.parsing.*;
import asteroids.programs.Program;
import asteroids.statements.Statement;
import asteroids.types.Type;

public class Facade<E, S, T> implements IFacade<World, Ship, Asteroid, Bullet, Program> {

	@Override
	public World createWorld(double width, double height) {
		try {
			return new World(width, height);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getWorldWidth(World world) {
		try {
			return world.getWidth();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getWorldHeight(World world) {
		try {
			return world.getHeight();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Set<Ship> getShips(World world) {
		try {
			Set<Ship> result = new HashSet<Ship>();
			Set<SpaceEntity> spaceEntities = world.getSpaceEntities();
			for (SpaceEntity spaceEntity : spaceEntities) {
				if (isShip(spaceEntity))
					result.add((Ship) spaceEntity);
			}
			return result;
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Set<Asteroid> getAsteroids(World world) {
		try {
			Set<Asteroid> result = new HashSet<Asteroid>();
			Set<SpaceEntity> spaceEntities = world.getSpaceEntities();
			for (SpaceEntity spaceEntity : spaceEntities) {
				if (isAsteroid(spaceEntity))
					result.add((Asteroid) spaceEntity);
			}
			return result;
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Set<Bullet> getBullets(World world) {
		try {
			Set<Bullet> result = new HashSet<Bullet>();
			Set<SpaceEntity> spaceEntities = world.getSpaceEntities();
			for (SpaceEntity spaceEntity : spaceEntities) {
				if (isBullets(spaceEntity))
					result.add((Bullet) spaceEntity);
			}
			return result;
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void addShip(World world, Ship ship) {
		try {
			world.addSpaceEntity(ship);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void addAsteroid(World world, Asteroid asteroid) {
		try {
			world.addSpaceEntity(asteroid);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void removeShip(World world, Ship ship) {
		try {
			world.removeSpaceEntity(ship);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void removeAsteroid(World world, Asteroid asteroid) {
		try {
			world.removeSpaceEntity(asteroid);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void evolve(World world, double dt,
			CollisionListener collisionListener) {
		try {
			world.evolve(dt, collisionListener);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Ship createShip(double x, double y, double xVelocity,
			double yVelocity, double radius, double direction, double mass) {
		try {
			return new Ship(x, y, xVelocity, yVelocity, direction, mass,
					radius, null);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public boolean isShip(Object o) {
		try {
			return (o instanceof Ship);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipX(Ship ship) {
		try {
			return ship.getPosition().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipY(Ship ship) {
		try {
			return ship.getPosition().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipXVelocity(Ship ship) {
		try {
			return ship.getVelocity().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipYVelocity(Ship ship) {
		try {
			return ship.getVelocity().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipRadius(Ship ship) {
		try {
			return ship.getRadius();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipDirection(Ship ship) {
		try {
			return ship.getDirection();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getShipMass(Ship ship) {
		try {
			return ship.getMass();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public World getShipWorld(Ship ship) {
		try {
			return ship.getWorld();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public boolean isShipThrusterActive(Ship ship) {
		try {
			return ship.getStateOfThruster();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void setThrusterActive(Ship ship, boolean active) {
		try {
			ship.setStateOfThruster(active);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void turn(Ship ship, double angle) {
		try {
			ship.turn(angle);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public void fireBullet(Ship ship) {
		try {
			ship.fire();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,
			double yVelocity, double radius) {
		try {
			return new Asteroid(x, y, xVelocity, yVelocity, radius, null, null);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,
			double yVelocity, double radius, Random random) {
		try {
			return new Asteroid(x, y, xVelocity, yVelocity, radius, null, random);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public boolean isAsteroid(Object o) {
		try {
			return (o instanceof Asteroid);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidX(Asteroid asteroid) {
		try {
			return asteroid.getPosition().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidY(Asteroid asteroid) {
		try {
			return asteroid.getPosition().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidXVelocity(Asteroid asteroid) {
		try {
			return asteroid.getVelocity().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidYVelocity(Asteroid asteroid) {
		try {
			return asteroid.getVelocity().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidRadius(Asteroid asteroid) {
		try {
			return asteroid.getRadius();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getAsteroidMass(Asteroid asteroid) {
		try {
			return asteroid.getMass();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public World getAsteroidWorld(Asteroid asteroid) {
		try {
			return asteroid.getWorld();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public boolean isBullets(Object o) {
		try {
			return (o instanceof Bullet);
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletX(Bullet bullet) {
		try {
			return bullet.getPosition().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletY(Bullet bullet) {
		try {
			return bullet.getPosition().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletXVelocity(Bullet bullet) {
		try {
			return bullet.getVelocity().getX();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletYVelocity(Bullet bullet) {
		try {
			return bullet.getVelocity().getY();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletRadius(Bullet bullet) {
		try {
			return bullet.getRadius();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public double getBulletMass(Bullet bullet) {
		try {
			return bullet.getMass();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public World getBulletWorld(Bullet bullet) {
		try {
			return bullet.getWorld();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public Ship getBulletSource(Bullet bullet) {
		try {
			return bullet.getSource();
		} catch (Exception exc) {
			throw new ModelException("");
		}
	}

	@Override
	public ParseOutcome<Program> parseProgram(String text) {
		FactoryImplementation factory = new FactoryImplementation();
	    ProgramParser<E, S, T> parser = new ProgramParser(factory);
	    try {
	        parser.parse(text);
	        List<String> errors = parser.getErrors();
	        if(! errors.isEmpty()) {
	          return ParseOutcome.failure(errors.get(0));
	        } else {
	          return ParseOutcome.success(new Program((Map<String, Type>) parser.getGlobals(), (Statement) parser.getStatement())); 
	        }
	    } catch(RecognitionException e) {
	      return ParseOutcome.failure(e.getMessage());
	    }
	}

	@Override
	public ParseOutcome<Program> loadProgramFromStream(
			InputStream stream) throws IOException {
		StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
        String line = bufferedReader.readLine();
        while(line != null){
            inputStringBuilder.append(line);inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }

		return parseProgram(inputStringBuilder.toString());
	}

	@Override
	public ParseOutcome<Program> loadProgramFromUrl(URL url)
			throws IOException {
		return loadProgramFromStream(url.openStream());
	}

	@Override
	public boolean isTypeCheckingSupported() {
		return true;
	}

	@Override
	public TypeCheckOutcome typeCheckProgram(Program program) {
	    try {
	        List<String> errors = program.getTypeCheckErrors();
	        if(! errors.isEmpty()) {
	          return TypeCheckOutcome.failure(errors.get(0));
	        } else {
	          return TypeCheckOutcome.success(); 
	        }
	    } catch(RecognitionException e) {
	      return TypeCheckOutcome.failure(e.getMessage());
	    }
	}

	@Override
	public void setShipProgram(Ship ship, Program program) {
		ship.setProgram(program);
	}

}
